﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Castle.Services.Transaction;
using DecisionMaking.BusinessLogic.Model;
using DecisionMaking.Domain;
using NHibernate;
using NHibernate.Exceptions;

namespace DecisionMaking.BusinessLogic.Impl
{
    [Transactional]
    public class AlgorithmManager : IAlgorithmManager
    {
        public IAccountManager AccountManager { get; set; } // injected by Windsor

        [Transaction(TransactionMode.Supported)]
        public AlgorithmDefinition GetAlgorithmDefinition(int id)
        {
            try
            {
                return AlgorithmDefinition.Find(id);
            }
            catch (ObjectNotFoundException e)
            {
                throw new EntityNotFoundException(typeof(AlgorithmDefinition), id, e);
            }
        }

        [Transaction(TransactionMode.Supported)]
        public IList<AlgorithmDefinition> GetAlgorithmDefinitions(int accountId)
        {
            var superusers = Account.FindSuperUsers();

            var ids = (from a in superusers select a.Id).ToList();
            ids.Add(accountId);
            
            return AlgorithmDefinition.FindByAccountIds(ids);
        }

        [Transaction(TransactionMode.Requires)]
        public AlgorithmDefinition SaveAlgorithmDefinition(AlgorithmDefinitionModel model)
        {
            AlgorithmDefinition algorithmDefinition;

            // if algorithm definition is new, set account
            if (model.DefinitionId == null)
            {
                algorithmDefinition = new AlgorithmDefinition {Account = AccountManager.GetAccount(model.AccountId.GetValueOrDefault())};
            }
            else
            {
                // make sure user didn't try to change ownership of definition
                algorithmDefinition = GetAlgorithmDefinition(model.DefinitionId.GetValueOrDefault());
                if (algorithmDefinition.Account.Id != model.AccountId)
                {
                    throw new AccessDeniedException(model.AccountId.GetValueOrDefault(), typeof(AlgorithmDefinition), model.DefinitionId.Value);
                }		                
            }

            // verify that the engine specified corresponds to an IAlgorithm implementation
            var type = Type.GetType(model.Engine);
            if (type == null || !typeof(IAlgorithm).IsAssignableFrom(type))
            {
                throw new NoSuchAlgorithmEngineException(model.Engine);
            }
            else
            {
                algorithmDefinition.Engine = model.Engine;
                algorithmDefinition.Name = model.Name;
                algorithmDefinition.Code = model.Code;

                // validate algorithm
                var errors = GetAlgorithmInstance(algorithmDefinition).Validate();
                if (errors != null)
                {
                    throw new AlgorithmCompilationException(errors);
                }
                else
                {
                    try
                    {
                        algorithmDefinition.Save();
                    }
                    catch (ConstraintViolationException e)
                    {
                        if (e.ConstraintName == "uix_algorithm_definition_name")
                        {
                            throw new DuplicateAlgorithmDefinitionNameException(algorithmDefinition.Name, e);
                        }
                        else
                        {
                            throw;
                        }
                    }

                    return algorithmDefinition;                    
                }
            }
        }

        public IAlgorithm GetAlgorithmInstance(AlgorithmDefinition algorithmDefinition)
        {
            // construct algorithm from definition
            var engine = Type.GetType(algorithmDefinition.Engine);
            var algorithm = (IAlgorithm)Activator.CreateInstance(engine);
            // pass definition to algorithm
            algorithm.SetDefinition(algorithmDefinition);

            return algorithm;
        }

        public IDictionary<string, string> GetEngineNames()
        {
            var algorithmType = typeof(IAlgorithm);
            var assembly = Assembly.GetAssembly(algorithmType);
 
            // select all types derived from IAlgorithm
            var types = from t in assembly.GetTypes()
                        where (algorithmType.IsAssignableFrom(t) && !t.IsAbstract && t.IsClass)
                        select t;

            return (from t in types orderby t.Name select new {t.AssemblyQualifiedName, t.Name}).ToDictionary(k => k.Name, v => v.AssemblyQualifiedName);
   
        }

    }
}
